package pl.enigmatic.math;

import static pl.enigmatic.literal.collection.Set;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * not all methods are well defined :-) not all rules are well defined :-) TO BE CONTINUED. Good for static operations so far - if we put elements,
 * but not remove them. Questions:
 * <ul>
 * <li>what happens when we put an object for existing alias, not key?
 * <li>what happens when we remove mapping by key?
 * <li>what is the keySet? entrySet?
 * <li>are there more?
 * </ul>
 */
public class AliasedMap<K, V> implements Map<K, V> {

	private final Map<K, V> map;
	private final Map<K, K> project = new HashMap<K, K>();
	private final Map<K, Set<K>> aliases = new HashMap<K, Set<K>>();

	public AliasedMap(final Map<K, V> map) {
		this.map = map;
	}

	public AliasedMap() {
		this(new HashMap<K, V>());
	}

	@Override
	public void clear() {
		map.clear();
		project.clear();
		aliases.clear();
	}

	@Override
	public boolean containsKey(final Object key) {
		return aliases.containsKey(key);
	}

	@Override
	public boolean containsValue(final Object value) {
		return map.containsValue(value);
	}

	@Override
	public Set<java.util.Map.Entry<K, V>> entrySet() {
		return map.entrySet();
	}

	@Override
	public V get(final Object key) {
		if (!project.containsKey(key)) { return null; }
		return map.get(project.get(key));
	}

	@Override
	public boolean isEmpty() {
		return map.isEmpty();
	}

	@Override
	public Set<K> keySet() {
		return map.keySet();
	}

	public void addAliases(final K key, final K... alias) {// TODO rethink - for now only works without reassigning keys/aliases
		if (!aliases.containsKey(key)) {
			aliases.put(key, new HashSet<K>());
		}
		final Set<K> set = aliases.get(key);
		for (final K a : alias) {
			set.add(a);
			project.put(a, key);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public V put(final K key, final V value) {
		addAliases(key, key);
		return map.put(key, value);
	}

	@Override
	public void putAll(final Map<? extends K, ? extends V> m) {
		for (final java.util.Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
			put(e.getKey(), e.getValue());
		}
	}

	@Override
	public V remove(Object key) {
		key = aliases.get(key);
		project.values().removeAll(Set(key));
		aliases.remove(key);
		return map.remove(key);
	}

	@Override
	public int size() {
		return map.size();
	}

	@Override
	public Collection<V> values() {
		return map.values();
	}

}
